/*
 *  Copyright (c) 2013, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */

package com.facebook.rebound.ui;

import com.facebook.rebound.*;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;

import ohos.app.Context;

import ohos.global.resource.ResourceManager;

import ohos.multimodalinput.event.TouchEvent;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.facebook.rebound.ui.Util.*;

/**
 * The SpringConfiguratorView provides a reusable view for live-editing all registered springs
 * within an Application. Each registered Spring can be accessed by its id and its tension and
 * friction properties can be edited while the user tests the effected UI live.
 */
public class SpringConfiguratorView extends StackLayout {
    private static final int MAX_SEEKBAR_VAL = 100000;
    private static final float MIN_TENSION = 0;
    private static final float MAX_TENSION = 200;
    private static final float MIN_FRICTION = 0;
    private static final float MAX_FRICTION = 50;
    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#");

    private SpinnerAdapter spinnerAdapter;
    private final List<SpringConfig> mSpringConfigs = new ArrayList<SpringConfig>();
    private Spring mRevealerSpring;
    private float mStashPx;
    private float mRevealPx;
    private SpringConfigRegistry springConfigRegistry;
    private final int mTextColor = Color.argb(255, 225, 225, 225);
    private Slider mTensionSeekBar;
    private Slider mFrictionSeekBar;
    private Spinner mSpringSelectorSpinner;
    private Text mFrictionLabel;
    private Text mTensionLabel;
    private SpringConfig mSelectedSpringConfig;

    private AttrSet attrSet;

    public SpringConfiguratorView(Context context) {
        super(context);
    }

    public SpringConfiguratorView(Context context, AttrSet attrs) {
        this(context, attrs, null);
        this.attrSet = attrs;
    }

    public SpringConfiguratorView(Context context, AttrSet attrs, String defStyle) {
        super(context, attrs, defStyle);

        this.attrSet = attrs;

        SpringSystem springSystem = SpringSystem.create(context);
        springConfigRegistry = SpringConfigRegistry.getInstance();
        spinnerAdapter = new SpinnerAdapter(context);

        ResourceManager resources = getResourceManager();
        mRevealPx = dpToPx(40, resources);
        mStashPx = dpToPx(280, resources);

        mRevealerSpring = springSystem.createSpring();
        SpringListener revealerSpringListener = new RevealerSpringListener();
        mRevealerSpring
                .setCurrentValue(1)
                .setEndValue(1)
                .addListener(revealerSpringListener);

        addComponent(generateHierarchy(context));

        SeekbarListener seekbarListener = new SeekbarListener();
        mTensionSeekBar.setMaxValue(MAX_SEEKBAR_VAL);
        mTensionSeekBar.setValueChangedListener(seekbarListener);

        mFrictionSeekBar.setMaxValue(MAX_SEEKBAR_VAL);
        mFrictionSeekBar.setValueChangedListener(seekbarListener);

        mSpringSelectorSpinner.setItemProvider(spinnerAdapter);
        mSpringSelectorSpinner.setItemSelectedListener(new SpringSelectedListener());
        refreshSpringConfigurations();

        this.setTranslationY(mStashPx);
    }

    /**
     * Programmatically build up the view hierarchy to avoid the need for resources.
     * @param context Context
     * @return View hierarchy
     */
    private Component generateHierarchy(Context context) {
        ResourceManager resources = getResourceManager();

        StackLayout.LayoutConfig params;
        int fivePx = dpToPx(5, resources);
        int tenPx = dpToPx(10, resources);
        int twentyPx = dpToPx(20, resources);
        TableLayout.LayoutConfig tableLayoutParams = new TableLayout.LayoutConfig
                (TableLayout.LayoutConfig.MATCH_CONTENT, TableLayout.LayoutConfig.MATCH_CONTENT);
        tableLayoutParams.setMargins(0, 0, fivePx, 0);
        DirectionalLayout seekWrapper;

        StackLayout root = new StackLayout(context);
        params = (LayoutConfig) createLayoutConfig(getContext(), attrSet);
        root.setLayoutConfig(params);

        StackLayout container = new StackLayout(context);
        params = createMatchParams();
        params.setMargins(0, twentyPx, 0, 0);
        container.setLayoutConfig(params);
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.argb(100, 0, 0, 0)));
        container.setBackground(shapeElement);
        root.addComponent(container);

        mSpringSelectorSpinner = new Spinner(context);
        params = createMatchWrapParams();
        params.alignment = LayoutAlignment.TOP;
        params.setMargins(tenPx, tenPx, tenPx, 0);
        mSpringSelectorSpinner.setLayoutConfig(params);
        container.addComponent(mSpringSelectorSpinner);

        DirectionalLayout linearLayout = new DirectionalLayout(context);
        params = createMatchWrapParams();
        params.setMargins(0, 0, 0, dpToPx(80, resources));
        params.alignment = LayoutAlignment.BOTTOM;
        linearLayout.setLayoutConfig(params);
        linearLayout.setOrientation(DirectionalLayout.VERTICAL);
        container.addComponent(linearLayout);

        seekWrapper = new DirectionalLayout(context);
        params = createMatchWrapParams();
        params.setMargins(tenPx, tenPx, tenPx, twentyPx);
        seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
        seekWrapper.setLayoutConfig(params);
        seekWrapper.setOrientation(DirectionalLayout.HORIZONTAL);
        linearLayout.addComponent(seekWrapper);

        mTensionSeekBar = new Slider(context);
        mTensionSeekBar.setLayoutConfig(tableLayoutParams);
        seekWrapper.addComponent(mTensionSeekBar);

        mTensionLabel = new Text(getContext());
        mTensionLabel.setTextColor(new Color(mTextColor));
        params = (LayoutConfig) createLayoutConfig(getContext(), attrSet);//
        mTensionLabel.setTextAlignment(LayoutAlignment.VERTICAL_CENTER | LayoutAlignment.LEFT);
        mTensionLabel.setLayoutConfig(params);
        mTensionLabel.setMaxTextLines(1);
        seekWrapper.addComponent(mTensionLabel);

        seekWrapper = new DirectionalLayout(context);
        params = createMatchWrapParams();
        params.setMargins(tenPx, tenPx, tenPx, twentyPx);
        seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
        seekWrapper.setLayoutConfig(params);
        seekWrapper.setOrientation(DirectionalLayout.HORIZONTAL);
        linearLayout.addComponent(seekWrapper);

        mFrictionSeekBar = new Slider(context);
        mFrictionSeekBar.setLayoutConfig(tableLayoutParams);
        seekWrapper.addComponent(mFrictionSeekBar);

        mFrictionLabel = new Text(getContext());
        mFrictionLabel.setTextColor(new Color(mTextColor));
        params = (LayoutConfig) createLayoutConfig(getContext(), attrSet);//
        mFrictionLabel.setTextAlignment(LayoutAlignment.VERTICAL_CENTER | LayoutAlignment.LEFT);
        mFrictionLabel.setLayoutConfig(params);
        mFrictionLabel.setMaxTextLines(1);
        seekWrapper.addComponent(mFrictionLabel);

        Component nub = new Component(context);
        params = (LayoutConfig) createLayoutConfig(getContext(), attrSet);
        params.alignment = LayoutAlignment.TOP | LayoutAlignment.CENTER;
        nub.setLayoutConfig(params);
        nub.setTouchEventListener(new OnNubTouchListener());
        ShapeElement nubShapeElement = new ShapeElement();
        nubShapeElement.setRgbColor(RgbColor.fromArgbInt(Color.argb(255, 0, 164, 209)));
        nub.setBackground(nubShapeElement);
        root.addComponent(nub);
        return root;
    }

    /**
     * remove the configurator from its parent and clean up springs and listeners
     */
    public void destroy() {
        ComponentContainer parent = (ComponentContainer) getComponentParent();
        if (parent != null) {
            parent.removeComponent(this);
        }
        mRevealerSpring.destroy();
    }

    /**
     * reload the springs from the registry and update the UI
     */
    public void refreshSpringConfigurations() {
        Map<SpringConfig, String> springConfigMap = springConfigRegistry.getAllSpringConfig();

        spinnerAdapter.clear();
        mSpringConfigs.clear();

        for (Map.Entry<SpringConfig, String> entry : springConfigMap.entrySet()) {
            if (entry.getKey() == SpringConfig.defaultConfig) {
                continue;
            }
            mSpringConfigs.add(entry.getKey());
            spinnerAdapter.add(entry.getValue());
        }
        // Add the default config in last.
        mSpringConfigs.add(SpringConfig.defaultConfig);
        spinnerAdapter.add(springConfigMap.get(SpringConfig.defaultConfig));
        spinnerAdapter.notify();
        if (mSpringConfigs.size() > 0) {
            mSpringSelectorSpinner.setSelectedItemIndex(0);
        }
    }

    private class SpringSelectedListener implements ListContainer.ItemSelectedListener {
        @Override
        public void onItemSelected(ListContainer listContainer, Component component, int i, long l) {
            mSelectedSpringConfig = mSpringConfigs.get(i);
            updateSeekBarsForSpringConfig(mSelectedSpringConfig);
        }
    }

    /**
     * listen to events on seekbars and update registered springs accordingly
     */
    private class SeekbarListener implements Slider.ValueChangedListener {

        @Override
        public void onProgressUpdated(Slider slider, int val, boolean b) {
            float tensionRange = MAX_TENSION - MIN_TENSION;
            float frictionRange = MAX_FRICTION - MIN_FRICTION;

            if (slider == mTensionSeekBar) {
                float scaledTension = ((val) * tensionRange) / MAX_SEEKBAR_VAL + MIN_TENSION;
                mSelectedSpringConfig.tension =
                        OrigamiValueConverter.tensionFromOrigamiValue(scaledTension);
                String roundedTensionLabel = DECIMAL_FORMAT.format(scaledTension);
                mTensionLabel.setText("T:" + roundedTensionLabel);
            }

            if (slider == mFrictionSeekBar) {
                float scaledFriction = ((val) * frictionRange) / MAX_SEEKBAR_VAL + MIN_FRICTION;
                mSelectedSpringConfig.friction =
                        OrigamiValueConverter.frictionFromOrigamiValue(scaledFriction);
                String roundedFrictionLabel = DECIMAL_FORMAT.format(scaledFriction);
                mFrictionLabel.setText("F:" + roundedFrictionLabel);
            }
        }

        @Override
        public void onTouchStart(Slider slider) {

        }

        @Override
        public void onTouchEnd(Slider slider) {

        }
    }

    /**
     * update the position of the seekbars based on the spring value;
     *
     * @param springConfig current editing spring
     */
    private void updateSeekBarsForSpringConfig(SpringConfig springConfig) {
        float tension = (float) OrigamiValueConverter.origamiValueFromTension(springConfig.tension);
        float tensionRange = MAX_TENSION - MIN_TENSION;
        int scaledTension = Math.round(((tension - MIN_TENSION) * MAX_SEEKBAR_VAL) / tensionRange);

        float friction = (float) OrigamiValueConverter.origamiValueFromFriction(springConfig.friction);
        float frictionRange = MAX_FRICTION - MIN_FRICTION;
        int scaledFriction = Math.round(((friction - MIN_FRICTION) * MAX_SEEKBAR_VAL) / frictionRange);

        mTensionSeekBar.setProgressValue(scaledTension);
        mFrictionSeekBar.setProgressValue(scaledFriction);
    }

    /**
     * toggle visibility when the nub is tapped.
     */
    private class OnNubTouchListener implements Component.TouchEventListener {
        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            if (touchEvent.getAction() == TouchEvent.OTHER_POINT_DOWN) {
                togglePosition();
            }
            return true;
        }
    }

    private void togglePosition() {
        double currentValue = mRevealerSpring.getEndValue();
        mRevealerSpring
                .setEndValue(currentValue == 1 ? 0 : 1);
    }

    private class RevealerSpringListener implements SpringListener {

        @Override
        public void onSpringUpdate(Spring spring) {
            float val = (float) spring.getCurrentValue();
            float minTranslate = mRevealPx;
            float maxTranslate = mStashPx;
            float range = maxTranslate - minTranslate;
            float yTranslate = (val * range) + minTranslate;
            SpringConfiguratorView.this.setTranslationY(yTranslate);
        }

        @Override
        public void onSpringAtRest(Spring spring) {
        }

        @Override
        public void onSpringActivate(Spring spring) {
        }

        @Override
        public void onSpringEndStateChange(Spring spring) {
        }
    }

    private class SpinnerAdapter extends BaseItemProvider {

        private final Context mContext;
        private final List<String> mStrings;

        public SpinnerAdapter(Context context) {
            mContext = context;
            mStrings = new ArrayList<String>();
        }

        @Override
        public int getCount() {
            return mStrings.size();
        }

        @Override
        public Object getItem(int position) {
            return mStrings.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
            Text textView;
            if (component == null) {
                textView = new Text(mContext);
                ComponentContainer.LayoutConfig params = new ComponentContainer.LayoutConfig(
                        ComponentContainer.LayoutConfig.MATCH_PARENT,
                        ComponentContainer.LayoutConfig.MATCH_PARENT);
                textView.setLayoutConfig(params);//
                int twelvePx = dpToPx(12, getResourceManager());
                textView.setPadding(twelvePx, twelvePx, twelvePx, twelvePx);
                textView.setTextColor(new Color(mTextColor));
            } else {
                textView = (Text) component;
            }
            textView.setText(mStrings.get(position));
            return textView;
        }

        public void add(String string) {
            mStrings.add(string);
            notifyDataChanged();
        }

        /**
         * Remove all elements from the list.
         */
        public void clear() {
            mStrings.clear();
            notifyDataChanged();
        }
    }
}
